/*******************************************************************************
 * Copyright (c) 2006 CEA List.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     CEA List - initial API and implementation
 *******************************************************************************/
//Source file: H:\\temp\\generated\\gef\\AnchorMngr.java
package com.cea.papyrus.core.editpart.connection;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;

import org.eclipse.draw2d.ConnectionAnchor;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.ConnectionEditPart;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.Request;
import org.eclipse.gef.editparts.AbstractGraphicalEditPart;

import com.cea.papyrus.diagraminterchange2.di2.GraphConnector;

/**
 * Associates an anchor to a request or to a ConnectionEditPart.
 */
public abstract class AnchorMngr /* implements NodeEditPart */
{
	
	/**
	 * For test purpose.
	 */
	private ConnectionAnchor anchor;
	
	/**
	 * Anchors defined for the edit part.
	 */
	public Map<GraphConnector, ConnectionAnchor> anchors;
	
	/**
	 * the owner.
	 */
	private AbstractGraphicalEditPart ownerEditPart;
	
	/**
	 * Latest request processed. Use a WeakReference to allow garbage.
	 */
	private WeakReference<Request> lastRequest;
	
	/**
	 * Cached anchor for the latest request.
	 */
	private WeakReference<ConnectionAnchor> requestConnection;
	
	/**
	 * 
	 * 
	 * @param editPart 
	 */
	public AnchorMngr(AbstractGraphicalEditPart editPart) {
		ownerEditPart = editPart;
		installEditPolicy(editPart);
		anchors = new HashMap<GraphConnector, ConnectionAnchor>();
	}
	
	/**
	 * Return host of this manager, that is, the owner.
	 * 
	 * @return 
	 */
	public AbstractGraphicalEditPart getHost()
	{
		return ownerEditPart;
	}
	
	/**
	 * Get the anchor for the specified request. Look in the cache. Create it if
	 * needed.
	 * 
	 * @param request 
	 * 
	 * @return ConnectionAnchor
	 */
	public ConnectionAnchor getAnchor(Request request) {
		return createAnchor(request);
	}
	
	/**
	 * 
	 * 
	 * @param request 
	 * @param anchor 
	 */
	private void saveLastRequestAnchor(Request request, ConnectionAnchor anchor) {
		lastRequest = new WeakReference<Request>(request);
		requestConnection = new WeakReference<ConnectionAnchor>(anchor);
	}
	
	/**
	 * 
	 * 
	 * @return 
	 */
	private ConnectionAnchor getLastRequestAnchor() {
		if (requestConnection == null)
			return null;
		return requestConnection.get();
	}
	
	/**
	 * 
	 * 
	 * @return 
	 */
	private Object getLastRequest() {
		if (lastRequest == null)
			return null;
		return lastRequest.get();
	}
	
	/**
	 * Create an anchor for the specified request.
	 * 
	 * @param request 
	 * 
	 * @return ConnectionAnchor
	 */
	abstract public ConnectionAnchor createAnchor(Request request);
	
	/**
	 * Lookup an Anchor for the specified EditPart. Create it if needed.
	 * 
	 * @param connection 
	 * @param connector 
	 * 
	 * @return ConnectionAnchor
	 */
	public ConnectionAnchor getAnchor(ConnectionEditPart connection, GraphConnector connector) {
		ConnectionAnchor anchor = anchors.get(connector);
		if (anchor != null) {
			return anchor;
		}
//		Doesn't exist : create it
		anchor = createAnchor(connection, connector);
		anchors.put(connector, anchor);
		return anchor;
	}
	
	/**
	 * Create anchor for the specified ConnectionEditPart.
	 * 
	 * @param connection 
	 * @param connector 
	 * 
	 * @return ConnectionAnchor
	 */
	abstract public ConnectionAnchor createAnchor(ConnectionEditPart connection, GraphConnector connector);
	
	/**
	 * Install the editPolicy needed to manage connections.
	 * 
	 * @param editPart 
	 */
	public void installEditPolicy(EditPart editPart) {
//		not used here
	}
	
	/**
	 * 
	 * 
	 * @param connection 
	 * 
	 * @return ConnectionAnchor
	 */
	public ConnectionAnchor getSourceConnectionAnchor(ConnectionEditPart connection) {
		return getAnchor(connection, getSourceAnchorModel(connection));
	}
	
	/**
	 * 
	 * 
	 * @param connection 
	 * 
	 * @return ConnectionAnchor
	 */
	public ConnectionAnchor getTargetConnectionAnchor(ConnectionEditPart connection) {
		return getAnchor(connection, getTargetAnchorModel(connection));
	}
	
	/**
	 * 
	 * 
	 * @param request 
	 * 
	 * @return ConnectionAnchor
	 */
	public ConnectionAnchor getSourceConnectionAnchor(Request request) {
		return getAnchor(request);
	}
	
	/**
	 * 
	 * 
	 * @param request 
	 * 
	 * @return ConnectionAnchor
	 */
	public ConnectionAnchor getTargetConnectionAnchor(Request request) {
		return getAnchor(request);
	}
	
	/**
	 * Get the source anchor model of the connectionEditPart.
	 * 
	 * @param connection 
	 * 
	 * @return 
	 */
	private GraphConnector getSourceAnchorModel(ConnectionEditPart connection) {
		return ((GraphConnector) ((AnchorableGraphEdgeEditPart) connection).getGraphicalModel().getAnchor().get(0));
	}
	
	/**
	 * Get the target anchor model of the connectionEditPart.
	 * 
	 * @param connection 
	 * 
	 * @return 
	 */
	private GraphConnector getTargetAnchorModel(ConnectionEditPart connection) {
		return ((GraphConnector) ((AnchorableGraphEdgeEditPart) connection).getGraphicalModel().getAnchor().get(1));
	}
	
	/**
	 * Compute the anchor position for the specified position point.
	 * Return a new Point. The provided parameter is not modified.
	 * Point should be in absolute coordinate, because it is used in conjunction
	 * with the figure bounds from getBox().
	 * 
	 * @param reference The position used to compute the anchor..
	 * 
	 * @return 
	 */
	public Point getAnchorPosition(Point reference)
	{
		Rectangle box = getBox();
		return getAnchorPosition(reference, box);
	}
	
	/**
	 * Compute the anchor relative position for the specified relative position point.
	 * Return a new Point. The provided parameter is not modified.
	 * Point should be in absolute coordinate, because it is used in conjunction
	 * with the figure bounds from getBox().
	 * 
	 * @param reference The position used to compute the anchor..
	 * 
	 * @return 
	 */
	public Point getRelativeAnchorPosition(Point reference)
	{
		Rectangle box = getHost().getFigure().getBounds();
		return getAnchorPosition(reference, box);
	}
	
	/**
	 * Compute the anchor position for the specified position point and box.
	 * Return a new Point. The provided parameter is not modified.
	 * Both parameters should be in the same coordinate system.
	 * with the figure bounds.
	 * 
	 * @param box 
	 * @param reference The position used to compute the anchor..
	 * 
	 * @return 
	 */
	public abstract Point getAnchorPosition(Point reference, Rectangle box);
	
	/**
	 * Return the absolute position of the bounds of the figure.
	 * 
	 * @return 
	 */
	public Rectangle getBox()
	{
		IFigure figure = getHost().getFigure();
		Rectangle box = figure.getBounds().getCopy();
		figure.translateToAbsolute(box);
		return box;
	}
	
	/**
	 * Return the intersection between a line dranw with (center, reference) and the box border.
	 * 
	 * @param reference A reference that should be in the absolute
	 * coordinate system (same as getFigure.getBounds))
	 * 
	 * @return 
	 */
	public Point getBoxIntersect(Point reference) {
		return getBoxIntersect(reference, getHost().getFigure().getBounds());
	}
	
	/**
	 * Return the intersection between the box border and the line connecting box.center and reference and .
	 * 
	 * @param box 
	 * @param reference 
	 * 
	 * @return 
	 */
	public Point getBoxIntersect(Point reference, Rectangle box) {
		Rectangle r = Rectangle.SINGLETON;
		r.setBounds(box);
//		Add 1 in all directions
		r.translate(-1, -1);
		r.resize(1, 1);
//		compute center
		float centerX = r.x + 0.5f * r.width;
		float centerY = r.y + 0.5f * r.height;
		if (r.isEmpty() || (reference.x == (int)centerX && reference.y == (int)centerY))
			return new Point((int)centerX, (int)centerY);  //This avoids divide-by-zero
		float dx = reference.x - centerX;
		float dy = reference.y - centerY;
//		r.width, r.height, dx, and dy are guaranteed to be non-zero. 
		float scale = 0.5f / Math.max(Math.abs(dx) / r.width, Math.abs(dy) / r.height);
		dx *= scale;
		dy *= scale;
		centerX += dx;
		centerY += dy;
		return new Point(Math.round(centerX), Math.round(centerY));
	}
	
	/**
	 * Returns a point on the ellipse (defined by the owner's bounding box) where the
	 * connection should be anchored.
	 * 
	 * @param reference 
	 * 
	 * @return 
	 * 
	 * @see org.eclipse.draw2d.ConnectionAnchor#getLocation(Point)
	 */
	public Point getEllipseIntersect(Point reference) {
		return getEllipseIntersect(reference, getHost().getFigure().getBounds());
	}
	
	/**
	 * Returns a point on the ellipse (defined by the owner's bounding box) where the
	 * connection should be anchored.
	 * 
	 * @param box 
	 * @param reference 
	 * 
	 * @return 
	 * 
	 * @see org.eclipse.draw2d.ConnectionAnchor#getLocation(Point)
	 */
	public Point getEllipseIntersect(Point reference, Rectangle box) {
		Rectangle r = Rectangle.SINGLETON;
		r.setBounds(box);
		r.translate(-1, -1);
		r.resize(1, 1);
		Point ref = r.getCenter().negate().translate(reference);
		if (ref.x == 0)
			return new Point(reference.x, (ref.y > 0) ? r.bottom() : r.y);
		if (ref.y == 0)
			return new Point((ref.x > 0) ? r.right() : r.x, reference.y);
		float dx = (ref.x > 0) ? 0.5f : -0.5f;
		float dy = (ref.y > 0) ? 0.5f : -0.5f;
//		ref.x, ref.y, r.width, r.height != 0 => safe to proceed
		float k = (float)(ref.y * r.width) / (ref.x * r.height);
		k = k * k;
		return r.getCenter().translate((int)(r.width * dx / Math.sqrt(1 + k)),
				(int)(r.height * dy / Math.sqrt(1 + 1 / k)));
	}
}